#include "D3DData.h"

#include <cassert>

#include "main/Globals.h"
#include "main/WindowManager.h"

#include "managers/SettingsManager.h"

#include "utils/Camera.h"
#include "utils/DxErrorChecker.h"

using namespace DirectX;

Direct3DData::Direct3DData(WindowData& windowData)
    : mDevice(nullptr)
	, mImmediateContext(nullptr)
    , mSwapChain(nullptr)
    , mDepthStencilBuffer(nullptr)
    , mRenderTargetView(nullptr)
    , mDepthStencilView(nullptr)
    , mViewport(nullptr)
{
	createDeviceAndImmediateContext();
	
	// Compute MSAA quality and sample count
	uint32_t msaaQuality;
	uint32_t sampleCount;
	const DXGI_FORMAT displayFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
#if defined(DEBUG) | defined(_DEBUG)
	msaaQuality = 1;
	sampleCount = 1;
#else
	sampleCount = 4;
	assert(mDevice);
	const HRESULT result = mDevice->CheckMultisampleQualityLevels(displayFormat, sampleCount, &msaaQuality);
	DxErrorChecker(result);
#endif

	createSwapChain(windowData.mHandler, displayFormat, msaaQuality, sampleCount);
	
	createRenderTargetView();

	createDepthStencilBufferAndView(msaaQuality, sampleCount);

	// Bind depth stencil view and render target view
	// to the pipeline.
	{
		const size_t numViews = 1;
		assert(mRenderTargetView);
		assert(mDepthStencilView);
		mImmediateContext->OMSetRenderTargets(numViews, &mRenderTargetView, mDepthStencilView);
	}

	initViewport();

	initCamera();
}

Direct3DData::~Direct3DData() {
    mRenderTargetView->Release();
    mDepthStencilView->Release();
    mSwapChain->Release();
    mDepthStencilBuffer->Release();

    // Restore all default settings.
	mImmediateContext->ClearState();

	mImmediateContext->Release();
    mDevice->Release();
    delete mViewport;
}

void
Direct3DData::createDeviceAndImmediateContext() {
	assert(mDevice == nullptr);
	assert(mImmediateContext == nullptr);

	uint32_t createDeviceFlags;
#if defined(DEBUG) || defined(_DEBUG)  
	createDeviceFlags = D3D11_CREATE_DEVICE_DEBUG | D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
#else 
	createDeviceFlags = 0;
#endif

	D3D_FEATURE_LEVEL featureLevel;
	const HRESULT result = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, nullptr, 0, D3D11_SDK_VERSION, &mDevice, &featureLevel, &mImmediateContext);
	DxErrorChecker(result);

	if (featureLevel != D3D_FEATURE_LEVEL_11_0) {
		MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);
		exit(1);
	}

	assert(mDevice);
	assert(mImmediateContext);
}

void 
Direct3DData::createSwapChain(const HWND windowHandler, const DXGI_FORMAT displayFormat, const uint32_t msaaQuality, const uint32_t sampleCount) {
	// Fill swap chain description
	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

	assert(Globals::gSettingsManager);
	const uint32_t windowWidth = Globals::gSettingsManager->mWindowWidth;
	const uint32_t windowHeight = Globals::gSettingsManager->mWindowHeight;

	swapChainDesc.BufferDesc.Width = windowWidth;
	swapChainDesc.BufferDesc.Height = windowHeight;
	swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
	swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	swapChainDesc.BufferDesc.Format = displayFormat;
	swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

	// Multi-sampling parameters
	swapChainDesc.SampleDesc.Count = sampleCount;
	swapChainDesc.SampleDesc.Quality = msaaQuality - 1;

	// Describe the surface usage and CPU access options for the back buffer
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

	// Number of buffers in the swap chain
	swapChainDesc.BufferCount = 1;
	swapChainDesc.OutputWindow = windowHandler;
	swapChainDesc.Windowed = true;
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swapChainDesc.Flags = 0;

	// To correctly create the swap chain, we must use the IDXGIFactory that was
	// used to create the device.
	IDXGIDevice* dxgiDevice;
	assert(mDevice);
	HRESULT result = mDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**> (&dxgiDevice));
	DxErrorChecker(result);
	assert(dxgiDevice);

	IDXGIAdapter* dxgiAdapter;
	assert(dxgiDevice);
	result = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**> (&dxgiAdapter));
	DxErrorChecker(result);
	assert(dxgiAdapter);

	IDXGIFactory* dxgiFactory;
	assert(dxgiAdapter);
	result = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), reinterpret_cast<void**> (&dxgiFactory));
	DxErrorChecker(result);
	assert(dxgiFactory);

	assert(dxgiFactory);
	result = dxgiFactory->CreateSwapChain(mDevice, &swapChainDesc, &mSwapChain);
	DxErrorChecker(result);
	assert(mSwapChain);

	dxgiDevice->Release();
	dxgiAdapter->Release();
	dxgiFactory->Release();
}

void 
Direct3DData::createRenderTargetView() {
	ID3D11Texture2D* backBuffer;
	assert(mSwapChain);
	const size_t numBuffer = 0;
	HRESULT result = mSwapChain->GetBuffer(numBuffer, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer));
	DxErrorChecker(result);
	assert(backBuffer);

	assert(mDevice);
	result = mDevice->CreateRenderTargetView(backBuffer, nullptr, &mRenderTargetView);
	DxErrorChecker(result);
	assert(mRenderTargetView);
	backBuffer->Release();
}

void 
Direct3DData::createDepthStencilBufferAndView(const uint32_t msaaQuality, const uint32_t sampleCount) {
	// Fill depth stencil buffer description
	D3D11_TEXTURE2D_DESC texture2DDesc;
	ZeroMemory(&texture2DDesc, sizeof(D3D11_TEXTURE2D_DESC));

	const uint32_t windowWidth = Globals::gSettingsManager->mWindowWidth;
	const uint32_t windowHeight = Globals::gSettingsManager->mWindowHeight;

	texture2DDesc.Width = windowWidth;
	texture2DDesc.Height = windowHeight;
	texture2DDesc.MipLevels = 1;
	texture2DDesc.ArraySize = 1;
	texture2DDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;

	texture2DDesc.SampleDesc.Count = sampleCount;
	texture2DDesc.SampleDesc.Quality = msaaQuality - 1;

	texture2DDesc.Usage = D3D11_USAGE_DEFAULT;
	texture2DDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	texture2DDesc.CPUAccessFlags = 0;
	texture2DDesc.MiscFlags = 0;

	// Create depth stencil buffer
	assert(mDevice);
	HRESULT result = mDevice->CreateTexture2D(&texture2DDesc, nullptr, &mDepthStencilBuffer);
	DxErrorChecker(result);
	assert(mDepthStencilBuffer);

	// Create depth stencil view
	result = mDevice->CreateDepthStencilView(mDepthStencilBuffer, nullptr, &mDepthStencilView);
	DxErrorChecker(result);
	assert(mDepthStencilView);
}

void 
Direct3DData::initViewport() {
	const uint32_t windowWidth = Globals::gSettingsManager->mWindowWidth;
	const uint32_t windowHeight = Globals::gSettingsManager->mWindowHeight;

	mViewport = new D3D11_VIEWPORT();
	mViewport->TopLeftX = 0;
	mViewport->TopLeftY = 0;
	mViewport->Width = static_cast<float> (windowWidth);
	mViewport->Height = static_cast<float> (windowHeight);
	mViewport->MinDepth = 0.0f;
	mViewport->MaxDepth = 1.0f;

	assert(mImmediateContext);
	const size_t numViewports = 1;
	mImmediateContext->RSSetViewports(numViewports, mViewport);
}

void
Direct3DData::initCamera() {
	assert(Globals::gSettingsManager);
	const uint32_t windowWidth = Globals::gSettingsManager->mWindowWidth;
	const uint32_t windowHeight = Globals::gSettingsManager->mWindowHeight;

	const float fovY = 0.25f * XM_PI;
	const float aspectRatio = static_cast<float> (windowWidth) / windowHeight;
	const float nearPlaneZ = 1.0f;
	const float farPlaneZ = 1000.0f;
	Globals::gCamera.setFrustrum(fovY, aspectRatio, nearPlaneZ, farPlaneZ);
}